using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery.Test
{
    public static class QueryContextExtensions
    {
        public static IParam<Guid> CreateGuidParam(this QueryContext it)
        {
            return it.CreateParam<Guid>(name => new SqlParameter(name, SqlDbType.UniqueIdentifier), _ => _);
        }

        public static IParam<IOptionalValue<Guid>> CreateOptionalGuidParam(this QueryContext it)
        {
            return it.CreateParam<IOptionalValue<Guid>>(name => new SqlParameter(name, SqlDbType.UniqueIdentifier), GetValueOrDbNull);
        }

        public static IParam<string> CreateStringParam(this QueryContext it)
        {
            return it.CreateParam<string>(GetStringSqlParameterFunc(), _ => _);
        }

        private static Func<string, SqlParameter> GetStringSqlParameterFunc()
        {
            return name => new SqlParameter(name, SqlDbType.NVarChar) { Size = -1 };
        }

        public static IParam<IOptionalValue<string>> CreateOptionalStringParam(this QueryContext it)
        {
            return it.CreateParam<IOptionalValue<string>>(GetStringSqlParameterFunc(), GetValueOrDbNull);
        }

        public static IParam<int> CreateIntParam(this QueryContext it)
        {
            return it.CreateParam<int>(name => new SqlParameter(name, SqlDbType.Int), _ => _);
        }

        public static IParam<IOptionalValue<int>> CreateOptionalIntParam(this QueryContext it)
        {
            return it.CreateParam<IOptionalValue<int>>(name => new SqlParameter(name, SqlDbType.Int), GetValueOrDbNull);
        }

        public static IParam<Decimal<TPrecision, TScale>> CreateDecimalParam<TPrecision, TScale>(this QueryContext it)
            where TPrecision : IPrecision where TScale : IScale
        {
            return it.CreateParam<Decimal<TPrecision, TScale>>(
                name => new SqlParameter(name, SqlDbType.Decimal)
                            {
                                Precision = Decimal<TPrecision, TScale>.Precision,
                                Scale = Decimal<TPrecision, TScale>.Scale
                            },
                _ => _.Value);
        }

        public static IParam<NVarChar<TLength>> CreateNVarCharParam<TLength>(this QueryContext it) 
            where TLength : ILength
        {
            return it.CreateParam<NVarChar<TLength>>(GetStringSqlParameterFunc(), _ => _.Value);
        }

        public static IParam<IOptionalValue<NVarChar<TLength>>> CreateOptionalNVarCharParam<TLength>(this QueryContext it)
            where TLength : ILength
        {
            return it.CreateParam<IOptionalValue<NVarChar<TLength>>>(
                GetStringSqlParameterFunc(),
                _ => _.ProcessValue(nVarChar => nVarChar.Value).GetValueOrDbNull());
        }

        public static IParam<Money> CreateMoneyParam(this QueryContext it)
        {
            return it.CreateParam<Money>(name => new SqlParameter(name, SqlDbType.Money), _ => _.Value);
        }

        public static IParam<ITableValued<T>> CreateTableValuedParam<T>(this QueryContext it)
        {
            return it.CreateParam<ITableValued<T>>(
                parameterName => new SqlParameter(parameterName, SqlDbType.Structured)
                                     {
                                         TypeName = typeof (T).Name
                                     },
                _ => ConvertToDataTable(_.Value)
                );
        }

        public static IParam<Guid> CreateParam(this QueryContext it, Guid value)
        {
            var param = it.CreateGuidParam();
            param.Value = value;
            return param;
        }

        public static IParam<IOptionalValue<Guid>> CreateParam(this QueryContext it, IOptionalValue<Guid> value)
        {
            var param = it.CreateOptionalGuidParam();
            param.Value = value;
            return param;
        }

        public static IParam<string> CreateParam(this QueryContext it, string value)
        {
            var param = it.CreateStringParam();
            param.Value = value;
            return param;
        }

        public static IParam<IOptionalValue<string>> CreateParam(this QueryContext it, IOptionalValue<string> value)
        {
            var param = it.CreateOptionalStringParam();
            param.Value = value;
            return param;
        }

        public static IParam<int> CreateParam(this QueryContext it, int value)
        {
            var param = it.CreateIntParam();
            param.Value = value;
            return param;
        }

        public static IParam<Decimal<TPrecision, TScale>> CreateParam<TPrecision, TScale>(this QueryContext it, Decimal<TPrecision, TScale> value)
            where TPrecision : IPrecision where TScale : IScale
        {
            var param = it.CreateDecimalParam<TPrecision, TScale>();
            param.Value = value;
            return param;
        }

        public static IParam<NVarChar<TLength>> CreateParam<TLength>(this QueryContext it, NVarChar<TLength> value)
            where TLength : ILength
        {
            var param = it.CreateNVarCharParam<TLength>();
            param.Value = value;
            return param;
        }

        public static IParam<IOptionalValue<NVarChar<TLength>>> CreateParam<TLength>(this QueryContext it, IOptionalValue<NVarChar<TLength>> value)
            where TLength : ILength
        {
            var param = it.CreateOptionalNVarCharParam<TLength>();
            param.Value = value;
            return param;
        }

        public static IParam<Money> CreateParam(this QueryContext it, Money value)
        {
            var param = it.CreateMoneyParam();
            param.Value = value;
            return param;
        }

        public static IParam<IOptionalValue<int>> CreateParam(this QueryContext it, IOptionalValue<int> value)
        {
            var param = it.CreateOptionalIntParam();
            param.Value = value;
            return param;
        }

        public static IParam<ITableValued<T>> CreateParam<T>(this QueryContext it, ITableValued<T> value)
        {
            var param = it.CreateTableValuedParam<T>();
            param.Value = value;
            return param;
        }

        private static object GetValueOrDbNull<T>(this IOptionalValue<T> value)
        {
            return value.Process<object>(_ => _, () => DBNull.Value);
        }

        private static DataTable ConvertToDataTable<T>(IEnumerable<T> value)
        {
            var dataTable = new DataTable();
            var propertyInfos = typeof(T).GetProperties();
            foreach (var propertyInfo in propertyInfos)
            {
                dataTable.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
            }
            foreach (var item in value)
            {
                foreach (var propertyInfo in propertyInfos)
                {
                    var dataRow = dataTable.NewRow();
                    dataRow[propertyInfo.Name] = propertyInfo.GetValue(item, null);
                    dataTable.Rows.Add(dataRow);
                }
            }
            return dataTable;
        }
    }
}